package campus.aiit.ssl.service;

import campus.aiit.ssl.dao.DeviceModelDao;
import campus.aiit.ssl.dao.DeviceTypeDao;
import campus.aiit.ssl.dao.InstructDao;
import campus.aiit.ssl.dao.InstructTemplateDao;
import campus.aiit.ssl.domain.model.Instruct;
import campus.aiit.ssl.domain.model.InstructTemplate;
import campus.aiit.ssl.manage.custom.dao.DaoProxy;
import campus.aiit.ssl.manage.custom.CsvTemplateException;
import campus.aiit.ssl.manage.custom.SelectNpeException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.io.input.BOMInputStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.servlet.http.Part;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author ihaokun
 * @date 2019/12/4 11:14
 */
@Service
@Slf4j
public class InstructService{
  // .csv columns name
  private static final String TYPE = "设备类型";
  private static final String BRAND = "设备品牌";
  private static final String MODEL = "设备型号";
  private static final String NAME = "指令名称";
  private static final String VALUE = "红外指令值";

  private DeviceTypeDao typeDao;
  private DeviceModelDao modelDao;
  private InstructTemplateDao templateDao;
  private InstructDao instructDao;

  @Autowired
  public InstructService(DeviceTypeDao typeDao, DeviceModelDao modelDao, InstructTemplateDao templateDao, InstructDao instructDao) {
    this.typeDao = typeDao;
    this.modelDao = modelDao;
    this.templateDao = templateDao;
    this.instructDao = instructDao;
  }

  @PostConstruct
  public void afterConstruct(){
    DaoProxy<InstructTemplateDao> foo = new DaoProxy<>();
    templateDao = foo.getProxy(templateDao);
  }

  @Transactional
  public boolean uploadTemplate(String name, Part part){
    // 3 steps:
    //1 parse
    Iterable<CSVRecord> csvRecords = parseCsvFromInput(part);
    //2 check
    List<List<String>> matrix = checkCsvRecords(csvRecords);
    // split
    String[] strings = {matrix.get(0).get(0), matrix.get(1).get(0), matrix.get(2).get(0)};
    Map<String ,String> map = new HashMap<>();
    for (int i = 0; i < matrix.get(3).size(); i++) {
      map.put(matrix.get(3).get(i), matrix.get(4).get(i));
    }
    //3 data tables process
    return processSplitedData(name, strings, map);
  }

  private Iterable<CSVRecord> parseCsvFromInput(Part part){
    // Build Reader Instance from InputStream
    InputStream is;
    try {
      is = part.getInputStream();
    } catch (IOException e) {
      e.printStackTrace();
      return null;
    }
    //FIXED 由纯CSV改为了 BOM CSV，需要按照带BOM的解析
    Reader reader = new InputStreamReader(new BOMInputStream(is), StandardCharsets.UTF_8);
    try {
      // Parse CSV file, Header Auto Detection(First Record As Header)
      return CSVFormat.EXCEL.withFirstRecordAsHeader().parse(Objects.requireNonNull(reader));
    } catch (IOException e) {
      e.printStackTrace();
      return null;
    }
  }

  private List<List<String>> checkCsvRecords(Iterable<CSVRecord> records){
    //1 check iterator non-null & init matrix(for convert from iterator)
    Objects.requireNonNull(records);
    List<List<String>> result = new ArrayList<>();
    for (int i = 0; i < 5; i++) result.add(new ArrayList<>());
    // compare with the first instructionNames
    List<String> names = instructDao.firstInstructNames();
    //2 Traversal Iterator
    for (CSVRecord record : records) {
      long number = record.getRecordNumber();
      log.debug("record number: " + number);
      // 检查 CSV文件的三个列 设备类型、设备品牌、设备型号，是否值都相等
      // Accessing column value by key
      String type = record.get(TYPE);
      if (!result.get(0).isEmpty() && !result.get(0).contains(type)) throw new CsvTemplateException("第" + number + "行 设备类型 列值不匹配");
      String brand = record.get(BRAND);
      if (!result.get(1).isEmpty() && !result.get(1).contains(brand)) throw new CsvTemplateException("第" + number + "行 设备品牌 列值不匹配");
      String model = record.get(MODEL);
      if (!result.get(2).isEmpty() && !result.get(2).contains(model)) throw new CsvTemplateException("第" + number + "行 设备型号 列值不匹配");
      result.get(0).add(type);
      result.get(1).add(brand);
      result.get(2).add(model);
      //XXX instruct name cannot repeat
      if (result.get(3).contains(record.get(NAME))) {
        throw new CsvTemplateException("第" + number + "行" + NAME + "列值不可重复");
      }
      result.get(3).add(record.get(NAME));
      result.get(4).add(record.get(VALUE));
    }
    if (!names.isEmpty()){  //CRUX 注意list、array等，检测使用isEmpty()方式，不用 != null
      if (result.get(0).size() != names.size()){
        throw new CsvTemplateException("该模板指令集数量 不等于 统一模板指令集数量");
      }
      if (!result.get(3).containsAll(names)) {
        throw new CsvTemplateException("该模板指令集指令名 不符合 统一模板指令集指令名");
      }
    }
    return result;
  }

  // Read(device_type, device_model); write(instruct_template, instruct)
  private boolean processSplitedData(String name, String[] strings, Map<String, String> map){
    Long typeId = typeDao.selectIdByUniqueName(strings[0]);
    if (typeId == null) throw new SelectNpeException("device_type表中不存在由Unique(Name)确定的PrimaryKey");
    Long modelId = modelDao.selectIdByUniqueCombine(strings[1], strings[2]);
    if (modelId == null) throw new SelectNpeException("device_model表中不存在由Unique(BrandName, ModelName)确定的PrimaryKey");

    Long templateId = templateDao.selectIdByForeignKeys(typeId, modelId);
    if (templateId != null){
      throw new CsvTemplateException("instruct_template表中存在该条记录，不可覆盖");
    }
    InstructTemplate template = new InstructTemplate();
    template.setTypeId(typeId);
    template.setModelId(modelId);
    template.setName(name);
    templateDao.insertSelective(template);
    templateId = template.getId();

    Instruct instruct = new Instruct();
    instruct.setTemplateId(templateId);
    map.forEach((s, s2) -> {
      instruct.setName(s);
      instruct.setValue(s2);
      instructDao.insertSelective(instruct);
    });
    return true;
  }

  public InstructTemplate[] templates(){
    return templateDao.selectAll();
  }

  public InstructTemplate[] templates(long typeId, long modelId){
    return templateDao.selectByForeignKeys(typeId, modelId);
  }

  public Instruct[] instructs(long templateId) {
    return instructDao.selectByForeignKey(templateId);
  }

  public boolean modifyTemplate(long id, String name) {
    InstructTemplate template = new InstructTemplate();
    template.setId(id);
    template.setName(name);
    templateDao.updateByPrimaryKeySelective(template);
    return true;
  }

  @Transactional
  public boolean removeTemplate(long templateId) {
    // Foreign Key Restrict（cascade）
    instructDao.deleteByForeignKey(templateId);
    templateDao.deleteByPrimaryKey(templateId);
    return true;
  }

  public void savetime(Instruct instruct) {

    int i = this.instructDao.savetime(instruct);

    System.out.println(i);

  }
}